Shiny

Shiny is an R package that makes it easy to build interactive web apps straight from R. You can host standalone apps on a webpage or embed them in R Markdown documents or build dashboards. You can also extend your Shiny apps with CSS themes, htmlwidgets, and JavaScript actions.

Shiny is most often used with the packages “shinydashboard” or “flexdashboard” to build interactive dashboards. We will walk through how to use both shinydashboard and flexdashboard to give you options when using shiny.

Shiny Outline

If you were to open a Shiny app while creating a new file in R, an outline of the an app will be shown using the Old Faithful Geyser Data. This example app shows eruption wait times in a histogram with a side panel to change the number of bins in the histogram. A simple example of what these apps are intended for, user interaction.

## Old Faithful Geyser Example

library(shiny)

# Define UI for application that draws a histogram
ui <- fluidPage(

    # Application title
    titlePanel("Old Faithful Geyser Data"),

    # Sidebar with a slider input for number of bins 
    sidebarLayout(
        sidebarPanel(
            sliderInput("bins",
                        "Number of bins:",
                        min = 1,
                        max = 50,
                        value = 30)
        ),

        # Show a plot of the generated distribution
        mainPanel(
           plotOutput("distPlot")
        )
    )
)

# Define server logic required to draw a histogram
server <- function(input, output) {

    output$distPlot <- renderPlot({
        # generate bins based on input$bins from ui.R
        x    <- faithful[, 2]
        bins <- seq(min(x), max(x), length.out = input$bins + 1)

        # draw the histogram with the specified number of bins
        hist(x, breaks = bins, col = 'darkgray', border = 'white')
    })
}

# Run the application 
shinyApp(ui = ui, server = server)

Viewing this example can be useful to see the flow of the user interface and processes. So let’s go through what those terms mean. There are essentially three steps to any Shiny App:

  1. Create the User interface – ui
  2. Create the processes – server
  3. Run the app – shinyApp

The two main parts of the Shiny app are the ui and the server. These objects are the arguments used in the function to run the app, shinyApp(ui, server). We will create an ui and server object for these arguments and name them what they are, ui and server. This will help us from mixing up them up and make our lives easier.

Shiny and Shinydashboard

We will mostly be talking about shinydashboard in this walkthrough. There is no reason to worry as the two packages are essentially the same, but shinydashboard puts your code into an already styled theme in the ui without having to write the code yourself.

Shiny dashboard structure (UI - user interface)

The ui object controls the layout and appearance of the app. This includes formatting, color, text, input panels, main panels, etc. (maybe go into detail here). ui writes your code into html for you. Alternatively, you could use the html functions as well.

For shinydashboard we design parts of the ui and then combine it together in a function called dashboardPage(). To keep things simple, we will go through each of the main parts individually. Then show how they can be combined in this dashboardPage() to make the comeplete user interface.

3 Main parts:

  • Header - dashboardHeader()
  • Sidebar - dashboardSidebar()
  • Body - dashboardBody()

How to build a Sidebar Dashboard

library(shinydashboard)

sidebar <- dashboardSidebar(
  sidebarMenu(
    menuItem("Data",
      tabName = "data") #Used for linking in the body
  )
)

If you want more interactivity on your dashboard, shiny has some functions that enables you to do that. You can use a number of different input options using shiny. This allows the user of the website to input information - whether it’s text, a date, or to select options from data that you provide.

Different input functions: * selectInput() # Allows user to select a singular item from a list * textInput() # Allows user to input text * dateInput() # Allows user to input a date * checkboxInput()

How to build user input with shinydashboard

library("shiny")
library("tidyverse")

selectInput(
  inputId = "object", # This is the name you described the object elsewhere in the application
  label = "Favorite Character", # The label you want to show to the user
  choices = c(starwars$name) # List the choices the user has to pick from
 )

Render Functions

Functions that you use in your application’s server side code, assigning them to outputs that appear in your user interface.

  • renderPrint()
  • renderText()
  • renderPlot()
  • renderUI() # HTML or a shiny tag object

Example Using Star Wars df

library(shiny)
library(tidyverse)
library(shinydashboard)

header <- dashboardHeader(
  title = "Example Dashboard Header",
  titleWidth = 300
)

sidebar <- dashboardSidebar(
  selectInput(
    inputId = "name",
    label = "Favorite Character",
    choices = c(starwars$name)
  )
)

body <- dashboardBody(
  textOutput("name")
)

After you code the sidebar and body, you can then create the User Interface, or “ui”. The User Interface is built from a header, sidebar, and body. When you modify any of these options, the easiest way is to save it as an object called “header”, “sidebar”, or “body”. The user interface is most often coded into the dashboardPage() function.

ui <- dashboardPage(header = header,
                    sidebar = sidebar,
                    body = body
                    )

server <- function(input, output) {
  output$name <- renderText({
      input$name
    })
}

shinyApp(ui, server)

Shinydashboard Layout

There are two main types of layouts that shiny uses. The first is a “row” based layout, while the other is a “column” based layout. You can use either one, or even a mix of the two. The way to create the layouts is by using the “fluidRow()” function.

These functions are used inside of the dashboardBody() section. Each time “fluidRow()” is called, a new row is created.

Row layout

body <- dashboardBody(
  fluidRow(
# Row 1
  box(
  width = 12, # 12 width spans the entire width of the screen
  title = "Regular Box, Row 1",
  "Text inside of box"
  )),
  fluidRow(
# Row 2
  box(
  width = 12,
  title = "Regular Box, Row 2",
  "Text inside of box 2"
  )
)
)

ui <- dashboardPage(header = dashboardHeader(),
                    sidebar = dashboardSidebar(),
                    body = body
                    )

ui

Column Layout

The next layout option is to build columns. The difference in using columns instead of rows is to use the “column()” function inside of “fluidRow()”.

You can set the width of the column, but when you insert a box or chart, the width needs to be “NULL”.

body <- dashboardBody(
    fluidRow(
    #   Column 1
      column(width = 6,
      infoBox(
        width = NULL, # The width must be "NULL" when using a column layout
        title = "Regular Box, Column 1",
        "Text inside of box"
         )      
   ),
  column(width = 6,
    #   Column 2
      infoBox(
        width = NULL, # The width must be "NULL" when using a column layout
        title = "Regular Box, Column 2",
        "Text inside of box"
         )      
   )
  )
)
ui <- dashboardPage(header = dashboardHeader(),
                    sidebar = dashboardSidebar(),
                    body = body
                    )

shinyApp(ui, server)

Mix of Column and Row Layout

Note: To create a new row, call another fluidRow() function.

body <- dashboardBody(
    fluidRow(
    #   Row 1
      box(
        width = 12, # The width must be "NULL" when using a column layout
        title = "Regular Box, Row 1",
        "Text inside of box"
         ),
    ),
  fluidRow(
    column(width = 6,
    #   Column 2
      infoBox(
        width = NULL, # The width must be "NULL" when using a column layout
        title = "Regular Box, Row 2, Column 1",
        subtitle = "Text inside of box"
         )
    ),
    column(width = 6,
      infoBox(
        width = NULL,
        title = "Regular Box, Row 2, Column 2",
        subtitle = "Text inside of box"
      )
    )
  )
)

ui <- dashboardPage(header = dashboardHeader(),
                    sidebar = dashboardSidebar(),
                    body = body
                    )

shinyApp(ui, server)
LS0tDQp0aXRsZTogIlNoaW55IFdhbGt0aHJvdWdoIg0Kb3V0cHV0OiBodG1sX25vdGVib29rDQotLS0NCg0KIyBTaGlueQ0KDQpTaGlueSBpcyBhbiBSIHBhY2thZ2UgdGhhdCBtYWtlcyBpdCBlYXN5IHRvIGJ1aWxkIGludGVyYWN0aXZlIHdlYiBhcHBzIHN0cmFpZ2h0IGZyb20gUi4gWW91IGNhbiBob3N0IHN0YW5kYWxvbmUgYXBwcyBvbiBhIHdlYnBhZ2Ugb3IgZW1iZWQgdGhlbSBpbiBSIE1hcmtkb3duIGRvY3VtZW50cyBvciBidWlsZCBkYXNoYm9hcmRzLiBZb3UgY2FuIGFsc28gZXh0ZW5kIHlvdXIgU2hpbnkgYXBwcyB3aXRoIENTUyB0aGVtZXMsIGh0bWx3aWRnZXRzLCBhbmQgSmF2YVNjcmlwdCBhY3Rpb25zLg0KDQpTaGlueSBpcyBtb3N0IG9mdGVuIHVzZWQgd2l0aCB0aGUgcGFja2FnZXMgInNoaW55ZGFzaGJvYXJkIiBvciAiZmxleGRhc2hib2FyZCIgdG8gYnVpbGQgaW50ZXJhY3RpdmUgZGFzaGJvYXJkcy4gV2Ugd2lsbCB3YWxrIHRocm91Z2ggaG93IHRvIHVzZSBib3RoIHNoaW55ZGFzaGJvYXJkIGFuZCBmbGV4ZGFzaGJvYXJkIHRvIGdpdmUgeW91IG9wdGlvbnMgd2hlbiB1c2luZyBzaGlueS4NCg0KIyMgU2hpbnkgT3V0bGluZQ0KDQpJZiB5b3Ugd2VyZSB0byBvcGVuIGEgU2hpbnkgYXBwIHdoaWxlIGNyZWF0aW5nIGEgbmV3IGZpbGUgaW4gUiwgYW4gb3V0bGluZSBvZiB0aGUgYW4gYXBwIHdpbGwgYmUgc2hvd24gdXNpbmcgdGhlIE9sZCBGYWl0aGZ1bCBHZXlzZXIgRGF0YS4gVGhpcyBleGFtcGxlIGFwcCBzaG93cyBlcnVwdGlvbiB3YWl0IHRpbWVzIGluIGEgaGlzdG9ncmFtIHdpdGggYSBzaWRlIHBhbmVsIHRvIGNoYW5nZSB0aGUgbnVtYmVyIG9mIGJpbnMgaW4gdGhlIGhpc3RvZ3JhbS4gQSBzaW1wbGUgZXhhbXBsZSBvZiB3aGF0IHRoZXNlIGFwcHMgYXJlIGludGVuZGVkIGZvciwgdXNlciBpbnRlcmFjdGlvbi4gDQoNCmBgYHtyfQ0KIyMgT2xkIEZhaXRoZnVsIEdleXNlciBFeGFtcGxlDQoNCmxpYnJhcnkoc2hpbnkpDQoNCiMgRGVmaW5lIFVJIGZvciBhcHBsaWNhdGlvbiB0aGF0IGRyYXdzIGEgaGlzdG9ncmFtDQp1aSA8LSBmbHVpZFBhZ2UoDQoNCiAgICAjIEFwcGxpY2F0aW9uIHRpdGxlDQogICAgdGl0bGVQYW5lbCgiT2xkIEZhaXRoZnVsIEdleXNlciBEYXRhIiksDQoNCiAgICAjIFNpZGViYXIgd2l0aCBhIHNsaWRlciBpbnB1dCBmb3IgbnVtYmVyIG9mIGJpbnMgDQogICAgc2lkZWJhckxheW91dCgNCiAgICAgICAgc2lkZWJhclBhbmVsKA0KICAgICAgICAgICAgc2xpZGVySW5wdXQoImJpbnMiLA0KICAgICAgICAgICAgICAgICAgICAgICAgIk51bWJlciBvZiBiaW5zOiIsDQogICAgICAgICAgICAgICAgICAgICAgICBtaW4gPSAxLA0KICAgICAgICAgICAgICAgICAgICAgICAgbWF4ID0gNTAsDQogICAgICAgICAgICAgICAgICAgICAgICB2YWx1ZSA9IDMwKQ0KICAgICAgICApLA0KDQogICAgICAgICMgU2hvdyBhIHBsb3Qgb2YgdGhlIGdlbmVyYXRlZCBkaXN0cmlidXRpb24NCiAgICAgICAgbWFpblBhbmVsKA0KICAgICAgICAgICBwbG90T3V0cHV0KCJkaXN0UGxvdCIpDQogICAgICAgICkNCiAgICApDQopDQoNCiMgRGVmaW5lIHNlcnZlciBsb2dpYyByZXF1aXJlZCB0byBkcmF3IGEgaGlzdG9ncmFtDQpzZXJ2ZXIgPC0gZnVuY3Rpb24oaW5wdXQsIG91dHB1dCkgew0KDQogICAgb3V0cHV0JGRpc3RQbG90IDwtIHJlbmRlclBsb3Qoew0KICAgICAgICAjIGdlbmVyYXRlIGJpbnMgYmFzZWQgb24gaW5wdXQkYmlucyBmcm9tIHVpLlINCiAgICAgICAgeCAgICA8LSBmYWl0aGZ1bFssIDJdDQogICAgICAgIGJpbnMgPC0gc2VxKG1pbih4KSwgbWF4KHgpLCBsZW5ndGgub3V0ID0gaW5wdXQkYmlucyArIDEpDQoNCiAgICAgICAgIyBkcmF3IHRoZSBoaXN0b2dyYW0gd2l0aCB0aGUgc3BlY2lmaWVkIG51bWJlciBvZiBiaW5zDQogICAgICAgIGhpc3QoeCwgYnJlYWtzID0gYmlucywgY29sID0gJ2RhcmtncmF5JywgYm9yZGVyID0gJ3doaXRlJykNCiAgICB9KQ0KfQ0KDQojIFJ1biB0aGUgYXBwbGljYXRpb24gDQpzaGlueUFwcCh1aSA9IHVpLCBzZXJ2ZXIgPSBzZXJ2ZXIpDQoNCmBgYA0KDQpWaWV3aW5nIHRoaXMgZXhhbXBsZSBjYW4gYmUgdXNlZnVsIHRvIHNlZSB0aGUgZmxvdyBvZiB0aGUgdXNlciBpbnRlcmZhY2UgYW5kIHByb2Nlc3Nlcy4gU28gbGV0J3MgZ28gdGhyb3VnaCB3aGF0IHRob3NlIHRlcm1zIG1lYW4uIFRoZXJlIGFyZSBlc3NlbnRpYWxseSB0aHJlZSBzdGVwcyB0byBhbnkgU2hpbnkgQXBwOg0KDQoxLiBDcmVhdGUgdGhlIFVzZXIgaW50ZXJmYWNlIC0tIF9fdWlfXw0KMi4gQ3JlYXRlIHRoZSBwcm9jZXNzZXMgLS0gX19zZXJ2ZXJfXw0KMy4gUnVuIHRoZSBhcHAgLS0gX19zaGlueUFwcF9fDQoNClRoZSB0d28gbWFpbiBwYXJ0cyBvZiB0aGUgU2hpbnkgYXBwIGFyZSB0aGUgX191aV9fIGFuZCB0aGUgX19zZXJ2ZXJfXy4gVGhlc2Ugb2JqZWN0cyBhcmUgdGhlIGFyZ3VtZW50cyB1c2VkIGluIHRoZSBmdW5jdGlvbiB0byBydW4gdGhlIGFwcCwgX19zaGlueUFwcCh1aSwgc2VydmVyKV9fLiBXZSB3aWxsIGNyZWF0ZSBhbiB1aSBhbmQgc2VydmVyIG9iamVjdCBmb3IgdGhlc2UgYXJndW1lbnRzIGFuZCBuYW1lIHRoZW0gd2hhdCB0aGV5IGFyZSwgX3VpXyBhbmQgX3NlcnZlcl8uIFRoaXMgd2lsbCBoZWxwIHVzIGZyb20gbWl4aW5nIHVwIHRoZW0gdXAgYW5kIG1ha2Ugb3VyIGxpdmVzIGVhc2llci4NCg0KIyMgU2hpbnkgYW5kIFNoaW55ZGFzaGJvYXJkDQoNCldlIHdpbGwgbW9zdGx5IGJlIHRhbGtpbmcgYWJvdXQgc2hpbnlkYXNoYm9hcmQgaW4gdGhpcyB3YWxrdGhyb3VnaC4gVGhlcmUgaXMgbm8gcmVhc29uIHRvIHdvcnJ5IGFzIHRoZSB0d28gcGFja2FnZXMgYXJlIGVzc2VudGlhbGx5IHRoZSBzYW1lLCBidXQgc2hpbnlkYXNoYm9hcmQgcHV0cyB5b3VyIGNvZGUgaW50byBhbiBhbHJlYWR5IHN0eWxlZCB0aGVtZSBpbiB0aGUgdWkgd2l0aG91dCBoYXZpbmcgdG8gd3JpdGUgdGhlIGNvZGUgeW91cnNlbGYuIA0KDQoNCiMjIFNoaW55IGRhc2hib2FyZCBzdHJ1Y3R1cmUgKFVJIC0gdXNlciBpbnRlcmZhY2UpDQoNClRoZSBfX3VpX18gb2JqZWN0IGNvbnRyb2xzIHRoZSBsYXlvdXQgYW5kIGFwcGVhcmFuY2Ugb2YgdGhlIGFwcC4gVGhpcyBpbmNsdWRlcyBmb3JtYXR0aW5nLCBjb2xvciwgdGV4dCwgaW5wdXQgcGFuZWxzLCBtYWluIHBhbmVscywgZXRjLiAobWF5YmUgZ28gaW50byBkZXRhaWwgaGVyZSkuIHVpIHdyaXRlcyB5b3VyIGNvZGUgaW50byBodG1sIGZvciB5b3UuIEFsdGVybmF0aXZlbHksIHlvdSBjb3VsZCB1c2UgdGhlIGh0bWwgZnVuY3Rpb25zIGFzIHdlbGwuIA0KDQpGb3Igc2hpbnlkYXNoYm9hcmQgd2UgZGVzaWduIHBhcnRzIG9mIHRoZSB1aSBhbmQgdGhlbiBjb21iaW5lIGl0IHRvZ2V0aGVyIGluIGEgZnVuY3Rpb24gY2FsbGVkIGRhc2hib2FyZFBhZ2UoKS4gVG8ga2VlcCB0aGluZ3Mgc2ltcGxlLCB3ZSB3aWxsIGdvIHRocm91Z2ggZWFjaCBvZiB0aGUgbWFpbiBwYXJ0cyBpbmRpdmlkdWFsbHkuIFRoZW4gc2hvdyBob3cgdGhleSBjYW4gYmUgY29tYmluZWQgaW4gdGhpcyBkYXNoYm9hcmRQYWdlKCkgdG8gbWFrZSB0aGUgY29tZXBsZXRlIHVzZXIgaW50ZXJmYWNlLg0KDQozIE1haW4gcGFydHM6DQoNCiogSGVhZGVyIC0gZGFzaGJvYXJkSGVhZGVyKCkNCiogU2lkZWJhciAtIGRhc2hib2FyZFNpZGViYXIoKQ0KKiBCb2R5IC0gZGFzaGJvYXJkQm9keSgpDQoNCg0KIyMjIEhvdyB0byBidWlsZCBhIFNpZGViYXIgRGFzaGJvYXJkDQoNCmBgYHtyfQ0KbGlicmFyeShzaGlueWRhc2hib2FyZCkNCg0Kc2lkZWJhciA8LSBkYXNoYm9hcmRTaWRlYmFyKA0KICBzaWRlYmFyTWVudSgNCiAgICBtZW51SXRlbSgiRGF0YSIsDQogICAgICB0YWJOYW1lID0gImRhdGEiKSAjVXNlZCBmb3IgbGlua2luZyBpbiB0aGUgYm9keQ0KICApDQopDQpgYGANCg0KSWYgeW91IHdhbnQgbW9yZSBpbnRlcmFjdGl2aXR5IG9uIHlvdXIgZGFzaGJvYXJkLCBzaGlueSBoYXMgc29tZSBmdW5jdGlvbnMgdGhhdCBlbmFibGVzIHlvdSB0byBkbyB0aGF0LiBZb3UgY2FuIHVzZSBhIG51bWJlciBvZiBkaWZmZXJlbnQgaW5wdXQgb3B0aW9ucyB1c2luZyBzaGlueS4gVGhpcyBhbGxvd3MgdGhlIHVzZXIgb2YgdGhlIHdlYnNpdGUgdG8gaW5wdXQgaW5mb3JtYXRpb24gLSB3aGV0aGVyIGl0J3MgdGV4dCwgYSBkYXRlLCBvciB0byBzZWxlY3Qgb3B0aW9ucyBmcm9tIGRhdGEgdGhhdCB5b3UgcHJvdmlkZS4NCg0KRGlmZmVyZW50IGlucHV0IGZ1bmN0aW9uczoNCiogc2VsZWN0SW5wdXQoKSAjIEFsbG93cyB1c2VyIHRvIHNlbGVjdCBhIHNpbmd1bGFyIGl0ZW0gZnJvbSBhIGxpc3QNCiogdGV4dElucHV0KCkgIyBBbGxvd3MgdXNlciB0byBpbnB1dCB0ZXh0DQoqIGRhdGVJbnB1dCgpICMgQWxsb3dzIHVzZXIgdG8gaW5wdXQgYSBkYXRlDQoqIGNoZWNrYm94SW5wdXQoKQ0KDQoNCiMjIyBIb3cgdG8gYnVpbGQgdXNlciBpbnB1dCB3aXRoIHNoaW55ZGFzaGJvYXJkDQoNCmBgYHtyfQ0KbGlicmFyeSgic2hpbnkiKQ0KbGlicmFyeSgidGlkeXZlcnNlIikNCg0Kc2VsZWN0SW5wdXQoDQogIGlucHV0SWQgPSAib2JqZWN0IiwgIyBUaGlzIGlzIHRoZSBuYW1lIHlvdSBkZXNjcmliZWQgdGhlIG9iamVjdCBlbHNld2hlcmUgaW4gdGhlIGFwcGxpY2F0aW9uDQogIGxhYmVsID0gIkZhdm9yaXRlIENoYXJhY3RlciIsICMgVGhlIGxhYmVsIHlvdSB3YW50IHRvIHNob3cgdG8gdGhlIHVzZXINCiAgY2hvaWNlcyA9IGMoc3RhcndhcnMkbmFtZSkgIyBMaXN0IHRoZSBjaG9pY2VzIHRoZSB1c2VyIGhhcyB0byBwaWNrIGZyb20NCiApDQpgYGANCg0KDQojIyMgUmVuZGVyIEZ1bmN0aW9ucyANCkZ1bmN0aW9ucyB0aGF0IHlvdSB1c2UgaW4geW91ciBhcHBsaWNhdGlvbidzIHNlcnZlciBzaWRlIGNvZGUsIGFzc2lnbmluZyB0aGVtIHRvIG91dHB1dHMgdGhhdCBhcHBlYXIgaW4geW91ciB1c2VyIGludGVyZmFjZS4NCg0KKiByZW5kZXJQcmludCgpDQoqIHJlbmRlclRleHQoKQ0KKiByZW5kZXJQbG90KCkNCiogcmVuZGVyVUkoKSAjIEhUTUwgb3IgYSBzaGlueSB0YWcgb2JqZWN0DQoNCiMjIEV4YW1wbGUgVXNpbmcgU3RhciBXYXJzIGRmDQpgYGB7cn0NCmxpYnJhcnkoc2hpbnkpDQpsaWJyYXJ5KHRpZHl2ZXJzZSkNCmxpYnJhcnkoc2hpbnlkYXNoYm9hcmQpDQoNCmhlYWRlciA8LSBkYXNoYm9hcmRIZWFkZXIoDQogIHRpdGxlID0gIkV4YW1wbGUgRGFzaGJvYXJkIEhlYWRlciIsDQogIHRpdGxlV2lkdGggPSAzMDANCikNCg0Kc2lkZWJhciA8LSBkYXNoYm9hcmRTaWRlYmFyKA0KICBzZWxlY3RJbnB1dCgNCiAgICBpbnB1dElkID0gIm5hbWUiLA0KICAgIGxhYmVsID0gIkZhdm9yaXRlIENoYXJhY3RlciIsDQogICAgY2hvaWNlcyA9IGMoc3RhcndhcnMkbmFtZSkNCiAgKQ0KKQ0KDQpib2R5IDwtIGRhc2hib2FyZEJvZHkoDQogIHRleHRPdXRwdXQoIm5hbWUiKQ0KKQ0KYGBgDQoNCg0KQWZ0ZXIgeW91IGNvZGUgdGhlIHNpZGViYXIgYW5kIGJvZHksIHlvdSBjYW4gdGhlbiBjcmVhdGUgdGhlIFVzZXIgSW50ZXJmYWNlLCBvciAidWkiLiBUaGUgVXNlciBJbnRlcmZhY2UgaXMgYnVpbHQgZnJvbSBhIGhlYWRlciwgc2lkZWJhciwgYW5kIGJvZHkuIFdoZW4geW91IG1vZGlmeSBhbnkgb2YgdGhlc2Ugb3B0aW9ucywgdGhlIGVhc2llc3Qgd2F5IGlzIHRvIHNhdmUgaXQgYXMgYW4gb2JqZWN0IGNhbGxlZCAiaGVhZGVyIiwgInNpZGViYXIiLCBvciAiYm9keSIuIFRoZSB1c2VyIGludGVyZmFjZSBpcyBtb3N0IG9mdGVuIGNvZGVkIGludG8gdGhlIGRhc2hib2FyZFBhZ2UoKSBmdW5jdGlvbi4NCg0KDQpgYGB7cn0NCnVpIDwtIGRhc2hib2FyZFBhZ2UoaGVhZGVyID0gaGVhZGVyLA0KICAgICAgICAgICAgICAgICAgICBzaWRlYmFyID0gc2lkZWJhciwNCiAgICAgICAgICAgICAgICAgICAgYm9keSA9IGJvZHkNCiAgICAgICAgICAgICAgICAgICAgKQ0KDQpzZXJ2ZXIgPC0gZnVuY3Rpb24oaW5wdXQsIG91dHB1dCkgew0KICBvdXRwdXQkbmFtZSA8LSByZW5kZXJUZXh0KHsNCiAgICAgIGlucHV0JG5hbWUNCiAgICB9KQ0KfQ0KDQpzaGlueUFwcCh1aSwgc2VydmVyKQ0KDQpgYGANCg0KDQoNCiMjIFNoaW55ZGFzaGJvYXJkIExheW91dA0KDQpUaGVyZSBhcmUgdHdvIG1haW4gdHlwZXMgb2YgbGF5b3V0cyB0aGF0IHNoaW55IHVzZXMuIFRoZSBmaXJzdCBpcyBhICJyb3ciIGJhc2VkIGxheW91dCwgd2hpbGUgdGhlIG90aGVyIGlzIGEgImNvbHVtbiIgYmFzZWQgbGF5b3V0LiBZb3UgY2FuIHVzZSBlaXRoZXIgb25lLCBvciBldmVuIGEgbWl4IG9mIHRoZSB0d28uDQpUaGUgd2F5IHRvIGNyZWF0ZSB0aGUgbGF5b3V0cyBpcyBieSB1c2luZyB0aGUgImZsdWlkUm93KCkiIGZ1bmN0aW9uLiANCg0KVGhlc2UgZnVuY3Rpb25zIGFyZSB1c2VkIGluc2lkZSBvZiB0aGUgZGFzaGJvYXJkQm9keSgpIHNlY3Rpb24uIEVhY2ggdGltZSAiZmx1aWRSb3coKSIgaXMgY2FsbGVkLCBhIG5ldyByb3cgaXMgY3JlYXRlZC4gDQoNCiMjIyBSb3cgbGF5b3V0DQpgYGB7cn0NCmJvZHkgPC0gZGFzaGJvYXJkQm9keSgNCiAgZmx1aWRSb3coDQojIFJvdyAxDQogIGJveCgNCiAgd2lkdGggPSAxMiwgIyAxMiB3aWR0aCBzcGFucyB0aGUgZW50aXJlIHdpZHRoIG9mIHRoZSBzY3JlZW4NCiAgdGl0bGUgPSAiUmVndWxhciBCb3gsIFJvdyAxIiwNCiAgIlRleHQgaW5zaWRlIG9mIGJveCINCiAgKSksDQogIGZsdWlkUm93KA0KIyBSb3cgMg0KICBib3goDQogIHdpZHRoID0gMTIsDQogIHRpdGxlID0gIlJlZ3VsYXIgQm94LCBSb3cgMiIsDQogICJUZXh0IGluc2lkZSBvZiBib3ggMiINCiAgKQ0KKQ0KKQ0KDQp1aSA8LSBkYXNoYm9hcmRQYWdlKGhlYWRlciA9IGRhc2hib2FyZEhlYWRlcigpLA0KICAgICAgICAgICAgICAgICAgICBzaWRlYmFyID0gZGFzaGJvYXJkU2lkZWJhcigpLA0KICAgICAgICAgICAgICAgICAgICBib2R5ID0gYm9keQ0KICAgICAgICAgICAgICAgICAgICApDQoNCnVpDQpgYGANCg0KIyMjIENvbHVtbiBMYXlvdXQNClRoZSBuZXh0IGxheW91dCBvcHRpb24gaXMgdG8gYnVpbGQgY29sdW1ucy4gVGhlIGRpZmZlcmVuY2UgaW4gdXNpbmcgY29sdW1ucyBpbnN0ZWFkIG9mIHJvd3MgaXMgdG8gdXNlIHRoZSAiY29sdW1uKCkiIGZ1bmN0aW9uIGluc2lkZSBvZiAiZmx1aWRSb3coKSIuIA0KDQpZb3UgY2FuIHNldCB0aGUgd2lkdGggb2YgdGhlIGNvbHVtbiwgYnV0IHdoZW4geW91IGluc2VydCBhIGJveCBvciBjaGFydCwgdGhlIHdpZHRoIG5lZWRzIHRvIGJlICJOVUxMIi4gDQoNCmBgYHtyfQ0KYm9keSA8LSBkYXNoYm9hcmRCb2R5KA0KICAgIGZsdWlkUm93KA0KICAgICMgICBDb2x1bW4gMQ0KICAgICAgY29sdW1uKHdpZHRoID0gNiwNCiAgICAgIGluZm9Cb3goDQogICAgICAgIHdpZHRoID0gTlVMTCwgIyBUaGUgd2lkdGggbXVzdCBiZSAiTlVMTCIgd2hlbiB1c2luZyBhIGNvbHVtbiBsYXlvdXQNCiAgICAgICAgdGl0bGUgPSAiUmVndWxhciBCb3gsIENvbHVtbiAxIiwNCiAgICAgICAgIlRleHQgaW5zaWRlIG9mIGJveCINCiAgICAgICAgICkgICAgICANCiAgICksDQogIGNvbHVtbih3aWR0aCA9IDYsDQogICAgIyAgIENvbHVtbiAyDQogICAgICBpbmZvQm94KA0KICAgICAgICB3aWR0aCA9IE5VTEwsICMgVGhlIHdpZHRoIG11c3QgYmUgIk5VTEwiIHdoZW4gdXNpbmcgYSBjb2x1bW4gbGF5b3V0DQogICAgICAgIHRpdGxlID0gIlJlZ3VsYXIgQm94LCBDb2x1bW4gMiIsDQogICAgICAgICJUZXh0IGluc2lkZSBvZiBib3giDQogICAgICAgICApICAgICAgDQogICApDQogICkNCikNCnVpIDwtIGRhc2hib2FyZFBhZ2UoaGVhZGVyID0gZGFzaGJvYXJkSGVhZGVyKCksDQogICAgICAgICAgICAgICAgICAgIHNpZGViYXIgPSBkYXNoYm9hcmRTaWRlYmFyKCksDQogICAgICAgICAgICAgICAgICAgIGJvZHkgPSBib2R5DQogICAgICAgICAgICAgICAgICAgICkNCg0Kc2hpbnlBcHAodWksIHNlcnZlcikNCmBgYA0KDQoNCiMjIyBNaXggb2YgQ29sdW1uIGFuZCBSb3cgTGF5b3V0DQpOb3RlOg0KVG8gY3JlYXRlIGEgbmV3IHJvdywgY2FsbCBhbm90aGVyIGZsdWlkUm93KCkgZnVuY3Rpb24uIA0KYGBge3J9DQpib2R5IDwtIGRhc2hib2FyZEJvZHkoDQogICAgZmx1aWRSb3coDQogICAgIyAgIFJvdyAxDQogICAgICBib3goDQogICAgICAgIHdpZHRoID0gMTIsICMgVGhlIHdpZHRoIG11c3QgYmUgIk5VTEwiIHdoZW4gdXNpbmcgYSBjb2x1bW4gbGF5b3V0DQogICAgICAgIHRpdGxlID0gIlJlZ3VsYXIgQm94LCBSb3cgMSIsDQogICAgICAgICJUZXh0IGluc2lkZSBvZiBib3giDQogICAgICAgICApLA0KICAgICksDQogIGZsdWlkUm93KA0KICAgIGNvbHVtbih3aWR0aCA9IDYsDQogICAgIyAgIENvbHVtbiAyDQogICAgICBpbmZvQm94KA0KICAgICAgICB3aWR0aCA9IE5VTEwsICMgVGhlIHdpZHRoIG11c3QgYmUgIk5VTEwiIHdoZW4gdXNpbmcgYSBjb2x1bW4gbGF5b3V0DQogICAgICAgIHRpdGxlID0gIlJlZ3VsYXIgQm94LCBSb3cgMiwgQ29sdW1uIDEiLA0KICAgICAgICBzdWJ0aXRsZSA9ICJUZXh0IGluc2lkZSBvZiBib3giDQogICAgICAgICApDQogICAgKSwNCiAgICBjb2x1bW4od2lkdGggPSA2LA0KICAgICAgaW5mb0JveCgNCiAgICAgICAgd2lkdGggPSBOVUxMLA0KICAgICAgICB0aXRsZSA9ICJSZWd1bGFyIEJveCwgUm93IDIsIENvbHVtbiAyIiwNCiAgICAgICAgc3VidGl0bGUgPSAiVGV4dCBpbnNpZGUgb2YgYm94Ig0KICAgICAgKQ0KICAgICkNCiAgKQ0KKQ0KDQp1aSA8LSBkYXNoYm9hcmRQYWdlKGhlYWRlciA9IGRhc2hib2FyZEhlYWRlcigpLA0KICAgICAgICAgICAgICAgICAgICBzaWRlYmFyID0gZGFzaGJvYXJkU2lkZWJhcigpLA0KICAgICAgICAgICAgICAgICAgICBib2R5ID0gYm9keQ0KICAgICAgICAgICAgICAgICAgICApDQoNCnNoaW55QXBwKHVpLCBzZXJ2ZXIpDQpgYGANCg0KDQoNCg0KDQo=